To build simple content search pages you can use the ContentSearchPage and its properties and methods, which makes it easy to do highlighted search and facet based filtering. This is integrated with blogs and html file content.
However, if you need to query other indexes, or you need to apply logic other then is supported by ContentSearchPage you should work with the SearchManager API. This gives you almost direct access to the underlying Elasticsearch engine.
Below is an example showing how to search the profile and content indexes.
See the end of this article for the full listing of the example
Note: In Kademi applications can provider their own means of indexing certain content items by providing AppIndexer's. An AppIndexer does not correlate exactly to an elasticsearch index, because multiple AppIndexers can use the same index, and AppIndexers can have multiple indexes within an account. For example the FileNodeIndexer maintains a seperate index for each branch of each repository, and ContactRequestAppIndexer stores data in the profile index so contact requests can be associated with the user profile documents they relate to.
Example
- Create a page which references our template
- Create a template to do the query
- Use the SearchManager API to execute the query in a server side js script section in the template
- Display the results using template code
1. Create a page which references the new search template
I created a page called searchPage.html in the website root folder
<html>
<head>
<link rel="template" href="theme/searchTest" />
</head>
<body>
</body>
</html>
2. Create a template to do the query
I next created /theme/searchTest.html to be the template for the page above, with skeleton code like this:
<html>
<head>
<title>search page</title>
</head>
<body>
JS CODE WILL GO HERE
<div class="container">
<h1>Search</h1>
DISPLAY RESULTS HERE
</div>
</body>
</html>
3. Use the SearchManager API to execute the query
We want to use server side JS to manipulate the SearchManager API, so we add the #script() directive, and we add a <script> tag inside that so our code will be nicely colour coded. And to begin with we'll just declare a variable for the search parameter and get it from the request:
#script()
<script>
var keyword = http.request.params.q;
// Add SearchManager stuff here
</script>
#end
Now we setup the elasticsearch query as a JSON object. This will just match on the keyword(s) in the request variable, and will get the nickName field (for profiles) and the title field (for html content pages). We also want a highlighted field to show the text that matched.
var json = {
"query": {
"match": {"_all":keyword}
},
"fields" : ["nickName", "title"],
"highlight": {
"fields" : {
"*" : {},
"content" : {
"type" : "plain"
}
}
}
};
// NEXT EXECUTE THE QUERY
Now for the fun bit. We need to get a reference to the AppIndexers we want to search on, use them to get a search query builder, set parameters on the search builder, and then execute it. Finally we will put the search result into a request attribute so the template can access it. We'll also add some logging so we can see in the website manager logs whats happening.
var sm = applications.search.searchManager;
var indexers = sm.appIndexers;
var profileIndexer = indexers.profile;
var contentIndexer = indexers.content;
log.info("using indexers {} {}", profileIndexer, contentIndexer);
var builder = sm.prepareSearch(profileIndexer, contentIndexer);
builder.setSource(JSON.stringify(json));
builder.setTypes("profile", "html");
var result = builder.execute().actionGet(); // <<--- execute the search!
log.info("result {}", result);
http.request.attributes.result = result; // make available to templating
Thats it for the search! Now we'll see how to display the results
4. Display the results using template code
First i want to tell the user how many search results we found and how many we're showing. For simplicitly i wont implement pagination in this example
<p class="pull-right lead">
Showing $request.attributes.result.hits.hits.size() of $request.attributes.result.hits.totalHits hits
</p>
And now we can generate a table showing the results, which will incude both types of documents, ie profiles and html content files
<table class="table table-striped">
#foreach( $hit in $request.attributes.result.hits)
<tr>
<td>
$!hit.fields.nickName.value // ONLY ONE OF THESE WILL BE AVAILABLE
$!hit.fields.title.value
</td>
<td>$hit.type</td> // SHOW THE DOCUMENT TYPE, profile or html
</tr>
#end
</table>
All done!!
And here's the result, showing 2 different searches...
Complete listing
Here's the full source code for the template used in this example
<html>
<head>
<title>search page</title>
</head>
<body>
#script()
<script>
var keyword = http.request.params.q;
var json = {
"query": {
"match": {"_all":keyword}
},
"fields" : ["nickName", "title"],
"highlight": {
"fields" : {
"*" : {},
"content" : {
"type" : "plain"
}
}
}
};
var sm = applications.search.searchManager;
var indexers = sm.appIndexers;
var profileIndexer = indexers.profile;
var contentIndexer = indexers.content;
log.info("using indexers {} {}", profileIndexer, contentIndexer);
var builder = sm.prepareSearch(profileIndexer, contentIndexer);
builder.setSource(JSON.stringify(json));
builder.setTypes("profile", "html");
var result = builder.execute().actionGet();
log.info("result {}", result);
http.request.attributes.result = result; // make available to templating
</script>
#end
<div class="container">
<h1>Search</h1>
<p class="pull-right lead">Showing $request.attributes.result.hits.hits.size() of $request.attributes.result.hits.totalHits hits</p>
<table class="table table-striped">
#foreach( $hit in $request.attributes.result.hits)
<tr>
<td>
$!hit.fields.nickName.value $!hit.fields.title.value
</td>
<td>$hit.type</td>
</tr>
#end
</table>
</div>
<!-- For debugging, display the search result as raw json -->
<pre>$request.attributes.result</pre>
</body>
</html>
Ask a question, or offer an answer